package com.iflytek.uoamp.agent.monitor.job;

import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;

import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.log4j.Logger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

import com.alibaba.fastjson.JSON;
import com.iflytek.uoamp.agent.core.sender.monitorsender.requestObj.ItemTypes;
import com.iflytek.uoamp.agent.core.sender.monitorsender.requestObj.MonitorItemConfig;
import com.iflytek.uoamp.agent.core.sender.monitorsender.requestObj.MonitorItemResult;

/**
 * @author gaowei 任务管理;负责安排任务时间;
 */
public class MCAgentJobManager {

	private static Map<JobKey, Set<String>> schedulerMonitorMap = new ConcurrentHashMap<JobKey, Set<String>>();
	private static String ITEMTYPE = ItemTypes.caseToString(ItemTypes.CHECKLOCAL);
	private static Map<String, Integer> failedTime = new HashMap<String, Integer>();
	private static final String FAILED_JOB = "FailedJob";
	private static Scheduler jobScheduler = null;
	private static Lock schedularMonitorMapLock = new ReentrantLock();

	private MCAgentJobManager() {

	}

	/**
	 * 是否需要重启
	 * 
	 * @param failedItems
	 * @param code
	 * @return
	 */
	private static boolean isRestart(String failedItems, String code) {
		boolean result = false;
		Integer recode = Integer.parseInt(code);
		Integer value = failedTime.get(failedItems);
		if (value == null) {
			value = Integer.valueOf(0);
		}

		if (recode == 3) {
			value = value + 1;
		} else {
			value = 0;
		}
		if (value > 3) {
			result = true;
			value = 0;
		}
		failedTime.put(failedItems, value);

		return result;
	}

	/**
	 * 处理失败的任务
	 * 
	 * @param curJobKey
	 * @param failedItems
	 */
	private static void parseFailedJob(JobKey curJobKey, MonitorItemResult failedItems) {

		Integer curFrequency;
		if (curJobKey.getName().contains(FAILED_JOB)) {
			curFrequency = getCurrentFailedJobRequency(curJobKey);
		} else {
			curFrequency = getCurrentJobRequency(curJobKey);
		}
		Integer requency = getNextFailedRequency(failedItems, curFrequency);
		Logger.getRootLogger().error(failedItems.getServerDesc() + " is fail !! and " + curFrequency + "   " + requency);
		// curjob is not fail job or is not in last queue
		// !curJobKey.getName().contains(FailedJob) ||
		if (!requency.equals(curFrequency)) {
			deleteJob(curJobKey, failedItems.getServerDesc());
			addJob(failedItems.getServerDesc(), requency, composeWarnJobKey(requency), requency);
			return;
		}

		// is fail job
		boolean isresstart = isRestart(failedItems.getServerDesc(), failedItems.getReturnCode());
		if (isresstart) {
			MonitorItemConfig config = Cache.readCache(MCAgentJobManager.ITEMTYPE, failedItems.getServerDesc());
			if (config.getXml() == null || config.getXml().isEmpty()) {
				Logger.getRootLogger().info("try restart " + failedItems.getServerDesc() + " but restart shell is empty,so skip it...");
				return;
			}

			ShellExecutor shellExrcutor = new ShellExecutor(config);
			shellExrcutor.restart();
			Logger.getRootLogger().info("try restart " + failedItems.getServerDesc() + " ...");
		}

	}

	/**
	 * 计算下一次更新时间
	 * 
	 * @param item
	 * @param currentRequency
	 * @return
	 */
	private static final Integer getNextFailedRequency(MonitorItemResult item, Integer currentRequency) {
		MonitorItemConfig itemConfig = Cache.readCache(ITEMTYPE, item.getServerDesc());
		return itemConfig.getNextRetryInterval(currentRequency);
	}

	/**
	 * 添加一个 job
	 * 
	 * @param itemName;
	 * @param requency;调用频率
	 * @param addedJobKey;
	 * @param delayMinute;延迟间隔;
	 */
	private static void addJob(String itemName, int requency, JobKey addedJobKey, long delayMinute) {
		if (itemName == null || itemName.isEmpty()) {
			Logger.getRootLogger().info("MCAgentManager addJobItemToMap  items is empty");
			return;
		}
		Logger.getRootLogger().info("parse addJobItemToMap " + addedJobKey.toString() + "   " + itemName);
		schedularMonitorMapLock.lock();
		try {
			if (schedulerMonitorMap.containsKey(addedJobKey)) {
				schedulerMonitorMap.get(addedJobKey).add(itemName);
			} else {
				Set<String> monitorList = new HashSet<String>();
				monitorList.add(itemName);
				try {
					createMonitorJob(requency, addedJobKey, monitorList, delayMinute);
				} catch (SchedulerException e) {
					Logger.getRootLogger().error("BMonitorManager addJobItemToMap " + addedJobKey.toString() + " current monitor item " + itemName + " : error", e);
				}
			}
			Logger.getRootLogger().info("BMonitorManager addJobItemToMap " + addedJobKey.toString() + " current monitor items " + schedulerMonitorMap.get(addedJobKey).size() + " :" + itemName);
		} catch (Exception e) {
			Logger.getRootLogger().error(e.getMessage(), e);
		} finally {
			schedularMonitorMapLock.unlock();
		}

	}

	/**
	 * 删除一个 job
	 * 
	 * @param curJobKey; 需要删除的 key(任务等级)
	 * @param deleteItem; 需要删除的对象(任务)
	 */
	private static void deleteJob(JobKey curJobKey, String deleteItem) {
		schedularMonitorMapLock.lock();
		try {
			if (deleteItem == null || deleteItem.isEmpty() || !schedulerMonitorMap.containsKey(curJobKey)) {
				return;
			}

			schedulerMonitorMap.get(curJobKey).remove(deleteItem);
			Logger.getRootLogger().info("ServerMonitorJob deleteJobItemsFromMap " + curJobKey.toString() + " remove items " + deleteItem);

			// if current job include no item ,stop this job
			if (schedulerMonitorMap.get(curJobKey).isEmpty()) {

				try {
					schedulerMonitorMap.remove(curJobKey);
					jobScheduler.deleteJob(curJobKey);
					Logger.getRootLogger().info("ServerMonitorJob deleteJobItemsFromMap " + curJobKey.getGroup() + " " + curJobKey.getName() + " has no any monitor item, job is terminated");
				} catch (SchedulerException e) {
					Logger.getRootLogger().error("ServerMonitorJob deleteJobItemsFromMap " + curJobKey.getGroup() + " " + curJobKey.getName() + " deleteJob throws exception:" + e.toString(), e);
				}
			} else {
				Logger.getRootLogger().info("ServerMonitorJob deleteJobItemsFromMap " + curJobKey.toString() + " current items " + schedulerMonitorMap.get(curJobKey).size() + " :" + schedulerMonitorMap.get(curJobKey));
			}
		} catch (Exception e) {
			Logger.getRootLogger().error(e.getMessage(), e);
		} finally {
			schedularMonitorMapLock.unlock();
		}
	}

	/**
	 * 根据下发的队列按时间安排任务队列
	 * 
	 * @param monitorInfo
	 * @return
	 */
	private static final Map<Integer, String> assignJobQueue(List<MonitorItemConfig> monitorInfo) {
		Map<Integer, String> taskRequencyMap = new HashMap<Integer, String>();
		for (MonitorItemConfig taskConf : monitorInfo) {
			if (!Cache.cacheItemConfFile(taskConf)) {
				Logger.getRootLogger().info(taskConf.getServerDesc() + "  is write error so skip it! ");
				continue;
			}

			if (taskRequencyMap.get(taskConf.getCallFrequency()) == null) {
				taskRequencyMap.put(taskConf.getCallFrequency(), taskConf.getServerDesc());
			} else {
				String taskNames = taskRequencyMap.get(taskConf.getCallFrequency());
				taskRequencyMap.put(taskConf.getCallFrequency(), taskNames + "," + taskConf.getServerDesc());
			}
		}
		return taskRequencyMap;
	}

	/**
	 * 添加任务到执行队列,如果任务的key已经存在，则追加，否则新建任务；
	 * 
	 * @param taskRequencyMap
	 * @throws SchedulerException
	 */
	private static final void addJobToScheduler(Map<Integer, String> taskRequencyMap) throws SchedulerException {
		Iterator<Entry<Integer, String>> it = taskRequencyMap.entrySet().iterator();
		while (it.hasNext()) {
			Entry<Integer, String> entry = it.next();
			JobKey jobKey = composeJobKey(entry.getKey());
			String monitorList = entry.getValue();
			Set<String> monitorArrayList = new HashSet<String>(Arrays.asList(monitorList.split(",")));
			try {
				if (schedulerMonitorMap.containsKey(jobKey)) {
					schedulerMonitorMap.get(jobKey).addAll(monitorArrayList);
				} else {
					createMonitorJob(entry.getKey(), jobKey, monitorArrayList, 0L);
				}
			} catch (Exception e) {
				Logger.getRootLogger().error(e, e);
			}
		}
	}

	private static final Integer getCurrentFailedJobRequency(JobKey jobKey) {
		return Integer.valueOf(jobKey.getName().split("-")[1].split("-")[0]);
	}

	private static final Integer getCurrentJobRequency(JobKey jobKey) {
		return Integer.valueOf(jobKey.getName().split("-")[0]);
	}

	private static final JobKey composeJobKey(Integer requencyValue) {
		return new JobKey(requencyValue + "-Job", ITEMTYPE);
	}

	private static JobKey composeWarnJobKey(Integer requencyValue) {
		return new JobKey(FAILED_JOB + "-" + requencyValue + "-Job", ITEMTYPE);
	}

	private static void showCurrentJobMonitorItems(JobKey jobKey) {
		schedularMonitorMapLock.lock();
		try {
			if (MCAgentJobManager.schedulerMonitorMap.containsKey(jobKey)) {
				Logger.getRootLogger().info(jobKey + " exists monitor items " + schedulerMonitorMap.get(jobKey).size() + " :" + schedulerMonitorMap.get(jobKey));
			} else {
				Logger.getRootLogger().info(jobKey + " job is canceled");
			}
		} catch (Exception e) {
			Logger.getRootLogger().error(e, e);
		} finally {
			schedularMonitorMapLock.unlock();
		}
	}

	// create monitor job
	/**
	 * 创建任务
	 * 
	 * @param requencyValue
	 * @param jobKey
	 * @param monitorList
	 * @param delayMinute
	 * @throws SchedulerException
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	private final static void createMonitorJob(int requencyValue, JobKey jobKey, Set<String> monitorList, long delayMinute) throws SchedulerException {
		JobBuilder jobBuilder = newJob(ServerMonitorJob.class);
		jobBuilder = jobBuilder.withIdentity(jobKey);
		jobBuilder = jobBuilder.usingJobData("RequencyValue", requencyValue + "");
		jobBuilder = jobBuilder.withDescription(jobKey.toString());
		JobDetail job = jobBuilder.build();

		SimpleScheduleBuilder scheduleBuilder = simpleSchedule();
		scheduleBuilder = scheduleBuilder.withIntervalInSeconds(requencyValue);
		scheduleBuilder = scheduleBuilder.repeatForever();

		TriggerBuilder triggerBuilder = newTrigger();
		triggerBuilder = triggerBuilder.withIdentity(jobKey + "-Trigger", ITEMTYPE);
		triggerBuilder = triggerBuilder.withSchedule(scheduleBuilder);
		triggerBuilder = triggerBuilder.startAt(new Date(new Date().getTime() + delayMinute * 1000));
		Trigger trigger = triggerBuilder.build();

		Logger.getRootLogger().info("MCAgentManager createMonitorJob RequencyTime = " + requencyValue + " minutes, MonitorServices = " + monitorList);
		schedulerMonitorMap.put(job.getKey(), monitorList);
		jobScheduler.scheduleJob(job, trigger);

	}

	public static String getITEMTYPE() {
		return ITEMTYPE;
	}

	public static void setITEMTYPE(String iTEMTYPE) {
		ITEMTYPE = iTEMTYPE;
	}

	public static Map<JobKey, Set<String>> getSchedulerMonitorMap() {
		return schedulerMonitorMap;
	}

	public static void setSchedulerMonitorMap(ConcurrentHashMap<JobKey, Set<String>> schedulerMonitorMap) {
		MCAgentJobManager.schedulerMonitorMap = schedulerMonitorMap;
	}

	/**
	 * start job
	 * */
	public static void startJob(List<MonitorItemConfig> monitorInfo) throws SchedulerException {
		if (monitorInfo == null || monitorInfo.size() == 0) {
			Logger.getRootLogger().error("MCAgentManager startJob but have't monitor info!");
			return;
		}

		Logger.getRootLogger().info("MCAgentManager startJob...");
		try {
			jobScheduler = new StdSchedulerFactory().getScheduler();
		} catch (Exception e) {
			Logger.getRootLogger().error(e.getMessage(), e);
		}
		// clean old data
		Cache.cleanCache();
//		Logger.getRootLogger().info("List<MonitorItemConfig>------------->" + JSON.toJSONString(monitorInfo));
		Logger.getRootLogger().info("MCAgentManager will startJob Schedule and Task exists " + monitorInfo.size() + " Monitor Items");

		Map<Integer, String> taskRequencyMap = assignJobQueue(monitorInfo);
		addJobToScheduler(taskRequencyMap);

		if (!jobScheduler.getTriggerGroupNames().isEmpty()) {
			jobScheduler.start();
			Logger.getRootLogger().info("MCAgentManager startJob finish!");
		} else {
			Logger.getRootLogger().info("MCAgentManager startJob but No Monitor Tasks");
		}
	}

	/**
	 * add dont need update object to sucess or failed queue if update .move to update queue
	 * */
	public static void parseResultAndReSchedularJob(JobKey jobKey, List<MonitorItemResult> monitorResults, Set<String> updateList) {
		Logger.getRootLogger().info("start parseResultAndReSchedularJob " + jobKey);
		for (MonitorItemResult result : monitorResults) {
			Logger.getRootLogger().info("start parseResultAndReSchedularJob and parse " + result.getServerDesc());
			// item in updatelist doesn't attent frequency change
			if (!updateList.contains(result.getServerDesc())) {
				// failed monitor item
				if (Integer.parseInt(result.getReturnCode()) != 0) {
//					parseFailedJob(jobKey, result);
				} else {
					parseSucessJob(jobKey, result);
				}
			}
		}

		showCurrentJobMonitorItems(jobKey);
	}

	/**
	 * if curJobKey is not FailJob ,just return ; if curJob is FailJob ,move success job to original list
	 * */
	public static void parseSucessJob(JobKey curJobKey, MonitorItemResult successItem) {
		if (!curJobKey.getName().contains(FAILED_JOB))
			return;

		MonitorItemConfig itemConfig = Cache.readCache(ITEMTYPE, successItem.getServerDesc());
		deleteJob(curJobKey, successItem.getServerDesc());
		addJob(successItem.getServerDesc(), itemConfig.getCallFrequency(), composeJobKey(itemConfig.getCallFrequency()), itemConfig.getCallFrequency());
	}
	
	/***
	 * if returen frequency not equal last time ,reschedular jobs this function will update file first , if monitor_url is empty .the monitor item will be delete and if frequency change (both of delete and real change) .this function will delete item and is real change frequency time. this function will add item to new frequency list
	 */
	public static void updateJobSchedular(JobKey currentJob, List<MonitorItemConfig> updateList) {
		if (updateList == null)
			return;

		List<MonitorItemConfig> newMonitorList = Cache.updateCache(updateList);

		/**
		 * delete modify call frequency items
		 * */
		for (MonitorItemConfig cur : updateList) {
			JobKey itemJobKey = composeJobKey(cur.getCallFrequency());
			if (!itemJobKey.equals(currentJob)) {
				deleteJob(currentJob, cur.getServerDesc());
			} else if (cur.getServerUrl() == null) {
				deleteJob(currentJob, cur.getServerDesc());
			}
		}
		for (MonitorItemConfig cur : newMonitorList) {
			JobKey itemJobKey = composeJobKey(cur.getCallFrequency());
			addJob(cur.getServerDesc(), cur.getCallFrequency(), itemJobKey, cur.getCallFrequency());
		}
	}
}